package android.view;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;

import androidx.annotation.Nullable;


/**
 * 20190307,support customer define PWDrawObjectHandler.
 * 20200708：reset architecture for new feature.
 */
public abstract class PWDrawObjectHandler {

    // 20200408: 由于我们会增加新的画图形参数，所以画图形部分定义放到最后，且保证已有项目
    // 的 手写(0) 和擦除(5) OBJ 定义不变.
    public static final int DRAW_OBJ_RANDOM = 0; // pen-write.
    public static final int DRAW_OBJ_CHOICERASE = 1;
    public static final int DRAW_OBJ_FLYBLOSSHOVER = 2;
    public static final int DRAW_OBJ_CLICKERASE = 2; // 20200702 add.点击笔迹擦除一笔。
    public static final int DRAW_OBJ_FLYBLOSSERASE = 3; //20200412:current is just Erase,but reserve here.
    public static final int DRAW_OBJ_UNDO = 4;
    public static final int DRAW_OBJ_CHOICEMOVE = 4;   // 圈选之后移动位置.
    public static final int DRAW_OBJ_ERASE = 5;    // erase

    public static final int DRAW_OBJ_LINE = 6; // DRAW line
    public static final int DRAW_OBJ_TRIANGLE = 7; // draw triangle
    public static final int DRAW_OBJ_CIRCLE = 8; // draw circle
    public static final int DRAW_OBJ_RECTANGLE = 9; // draw rectangle.
    public static final int DRAW_OBJ_OVAL = 10;

    public static final int DRAW_OBJ_REGTRIGLE = 11;  // regular triangle(正三角形)
    public static final int DRAW_OBJ_ISOSTRIGLE = 12;  // isosceles triangle(等腰三角形)
    public static final int DRAW_OBJ_HEXAGON = 13;      // hexagon (正六边形)

    public static final int DRAW_OBJ_BMPOBJ = 14;   // 插入到 图片 OBJ.
    public static final int DRAW_OBJ_TXTOBJ = 15;   // 插入的 文本 OBJ。
    public static final int DRAW_OBJ_PWOBJ = 16;    // 圈选之后获取到的OBJ
    public static final int DRAW_OBJ_MAX = DRAW_OBJ_PWOBJ;    // the max obj type.


    protected int mDrawObjectType = -1;
    protected EinkPWInterface mEinkPWInterface;
    protected Paint mDrawObjectPaint;

    // 20200416:use for some special draw-obj.
    public static final int DRAWOBJ_FLAG_RESETPOINTS = (1 << 0);  // may only needs DOWN/UP points.support fast repaint.
    public static final int DRAWOBJ_FLAG_PREPARE_HOSTPW = (1 << 1);
    public static final int DRAWOBJ_FLAG_PREPARE_HOSTPURE = (1 << 2);
    public static final int DRAWOBJ_FLAG_PREPARE_FLOATBMP = (1 << 3); // 20200412,add.
    public static final int DRAWOBJ_FLAG_WORK_HEAVYX = (1 << 4); // 20200708,affect the cpu freq,very heavy work.
    public static final int DRAWOBJ_FLAG_WORK_HEAVYN = (1 << 5); // normal heavy work.
    public static final int DRAWOBJ_FLAG_FREEPOINTS = (1 << 6); // free all points,like clickErase.
    public static final int DRAWOBJ_FLAG_FINEPOINTS = (1 << 7);  // 20200718: need fine x/y position.
    public static final int DRAWOBJ_FLAG_ERASEPAINT = (1 << 8);  // 20200806: this is handler about erase.
    protected int mDrawObjectFlag = 0;

    public static String getObjTypeString(int objType) {
        switch (objType) {
            case DRAW_OBJ_RANDOM:
                return "PW_OBJ_RANDOM";
            case DRAW_OBJ_CHOICERASE:
                return "DRAW_OBJ_CHOICERASE";
            case DRAW_OBJ_FLYBLOSSHOVER:
                return "DRAW_OBJ_CLICKERASE";//"DRAW_OBJ_FLYBLOSSHOVER";
            case DRAW_OBJ_FLYBLOSSERASE:
                return "DRAW_OBJ_FLYBLOSSERASE";
            case DRAW_OBJ_UNDO:
                return "DRAW_OBJ_CHOICEMOVE"; //"PW_OBJ_UNDO";
            case DRAW_OBJ_ERASE:
                return "PW_OBJ_ERASE";


            case DRAW_OBJ_LINE:
                return "PW_OBJ_LINE";
            case DRAW_OBJ_TRIANGLE:
                return "PW_OBJ_TRIANGLE";
            case DRAW_OBJ_CIRCLE:
                return "PW_OBJ_CIRCLE";
            case DRAW_OBJ_RECTANGLE:
                return "PW_OBJ_RECTANGLE";
            case DRAW_OBJ_OVAL:
                return "DRAW_OBJ_OVAL";

            case DRAW_OBJ_REGTRIGLE:
                return "DRAW_OBJ_REGTRIGLE";
            case DRAW_OBJ_ISOSTRIGLE:
                return "DRAW_OBJ_ISOSTRIGLE";
            case DRAW_OBJ_HEXAGON:
                return "DRAW_OBJ_HEXAGON";
            case DRAW_OBJ_BMPOBJ:
                return "DRAW_OBJ_BMPOBJ";
            case DRAW_OBJ_TXTOBJ:
                return "DRAW_OBJ_TXTOBJ";
            case DRAW_OBJ_PWOBJ:
                return "DRAW_OBJ_PWOBJ";
        }
        return "PW_OJB_UNKNOW";
    }

    /**
     * 默认构造函数，必须包含一个基本数据类型: mDrawObjectType.
     *
     * @param objType 我们当前的 draw Handler 对应的 objectType.
     */
    public PWDrawObjectHandler(int objType, int objFlag) {
        mDrawObjectType = objType;
        mDrawObjectFlag = objFlag;
    }

    public int getDrawObjectType() {
        return mDrawObjectType;
    }

    public int getDrawObjectFlag() {
        return mDrawObjectFlag;
    }

    public Paint getDrawObjectPaint() {
        return mDrawObjectPaint;
    }

    public void setDrawObjectPaint(Paint paint) {
        mDrawObjectPaint = paint;
    }

    public void setPWInterface(EinkPWInterface pwInterface) {
        mEinkPWInterface = pwInterface;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder(32);
        sb.append("ObjectHandle(");
        sb.append(getObjTypeString(mDrawObjectType));
        sb.append("-0x" + Integer.toHexString(mDrawObjectFlag));
        sb.append(")");
        return sb.toString();
    }

    // 20200408：我们计算延迟操作，直接通过 repeat 来处理。由于不同的笔有不同的报点率，所以此处定义
    // 一个统一的函数，便于维护.
    public int getMsRepeatCnt(int ms) {
        return (ms + 3) >> 2;  // 默认是 4ms 上报一个事件。
    }

    // 20200407: 当系统的配置发生变化，比如 笔宽，颜色等，对应的 draw object 可能需要做些处理。
    public void onConfigChange() {
    }

    /**
     * 根据 curPoint 的压感值和speed值以及和 当前点和前面点的关系来计算当前节点的 radus 大小。
     *
     * @return 返回半径的大小。	返回 UNCALC_RADUS_VALUE 表示该函数未实现.
     * 可以通过 curPoint.getPrev 获取到上一个输入点的信息。 可以通过 curPoint.getPress 获取压感值，通过    curPoint.getSpeed 获取速度值。
     * 这个函数在 draw curPoint 之前被调用。
     * 20200326: 由于笔迹的大小和笔的类型有很大的关系，所以增加一个代表笔类型的参数.且默认直接返回 penWidth。
     */
    public float calcPointRadus(PWInputPoint curPoint, float penWidth, float eraseWidth, int penType) {
        return penWidth;
    }


    /* 本次 draw 没有任何更新操作，不需要刷新屏幕 */
    public static final int DRAWOBJ_RET_NODRAW = 0;

    /* 本次 draw 操作需要 draw pw-bitmap  */
    public static final int DRAWOBJ_RET_DRAWPW = 0x01;

    /* 本次 draw 操作有擦除的区域（指原来draw出来的内容要擦除掉) */
    public static final int DRAWOBJ_RET_ERASE = 0x02;


    // 需要draw 一个 在 PW 之上的 float bitmap。大小和 PW bitmap 一样。
    public static final int DRAWOBJ_RET_DRAWFLOAT = 0x04;

    // 需要draw 一个 rect 大小的 FULL-OBJ-BITMAP。 --20200407：更好的办法可能还是draw 到 float bmp 上面。
    // 此处先保留。
    public static final int DRAWOBJ_RET_DRAWOBJ = 0x08;

    // 20200725,hsl add.
    public static final int DRAWOBJ_RET_CHECKAFFECT = 0x10;

    // 20200404: 这个标志会影响 更新区域的更新模式.一般情况下是 AUTO，如果定义了下面的标志，
    // 需要使用GC (有灰阶) 或者 DU，且做整个区块刷新.
    public static final int DRAWOBJ_RET_DISPFULL = 0x20;
    public static final int DRAWOBJ_RET_DISPFAST = 0x40;

    // 20200407：dirty-rect 里面各个 rect 需要单独用 AUTO 模式刷新。不能直接 auto bound.
    public static final int DRAWOBJ_RET_DISPAUTO = 0x80;

    // 20200407: MASK 之外的其他 BIT 值预留给系统其他方面使用。
    public static final int DRAWOBJ_RET_VALMASK = 0xFFF;


    // 20200407: 返回 DRAWOBJ_RET_DRAWOBJ 需要的bitmap。但是目前可以用 FLOAT 代替。此处先保留。
    public Bitmap getDrawObjectBitmap() {
        return null;
    }

    /**
     * 把当前的点 draw 到 canvas 上面，返回 draw的标志。
     * 把 curPoint 的显示 draw 到这个 canvas 上面。如果是 null,表示不需要 draw,只需要计算 dirtyRects 即可（比如用来处理笔迹选择)。
     * 需要 draw 的点，可以通过   curPoint.getPrev 获取到上一点的内容。 可以通过 curPoint.getRadus 获取到这个点经过压感处理后的半径大小。
     * 本次 draw 的时候需要使用到的 paint.可以定义自己的 paint.
     * 返回： DRAWOBJ_RET_XXXX.
     * 20200407：以前是 drawPoints 和 getDrawPointRects 分开。此处合并在一起，增加了  dirtyRects 参数。主要是用于复杂 object的情况.
     * 20200708: 增加参数 first.
     */
    public abstract int drawPoints(Canvas canvas, PWInputPoint first, PWInputPoint curPoint,
                                   @Nullable RectArray dirtyRects, Paint penPaint);

    // 20200404: 有些 handler 在笔没有移动的时候，需要做一些特殊处理。此时可以通过 lastPoint 的 repeat 来得到点重复的次数。
    public int drawSamePoint(Canvas canvas, PWInputPoint first, PWInputPoint lastPoint,
                             @Nullable RectArray dirtyRects, Paint penPaint) {
        return DRAWOBJ_RET_NODRAW;
    }

    // 20200404: some draw-object 需要通过 手写线程进行一些延迟操作。 param 表示自定义的参数.
    public int onDelayDraw(Canvas canvas, @Nullable RectArray dirtyRects, Paint penPaint, Object param) {
        return DRAWOBJ_RET_NODRAW;
    }


    // 20200416: 根据落笔点和提笔点，获取这个 drawObject的 dirty-rect。如果不存在常规图形的 rects,则 返回 false;
    // 20200708: 修改返回值是 void. 所有的 objHandle 都必须返回一个有效的 dirty-rect.系统在 ACTION-UP 的时候调用。
    public abstract void getDrawAffectRect(PWInputPoint first, PWInputPoint last, Rect result);

    // 20200725: 判断本次操作是否有效，主要用于擦除情况的优化。
    public boolean checkDrawAffective() {
        return true;
    }

    // 20200426: 需要判断一个 x/y 位置和 当前draw 图形的距离( 判断是否点击在该图形所画的笔迹上面 ),
    // 只有具有 DRAWOBJ_FLAG_RESETPOINTS 标志的 handler 才会进行这个判断。
    // 20200708：判断一个坐标点 X/Y 是否在指定的 OBJ 上面（里面/外面）。返回值分为 3个类型。 == 0 ,表示 X/Y
    // 落在画线上， < 0 : 表示落在封闭图形的内部， > 0: 表示落在封闭图形的外部，或者没有落在非封闭图形的边上。
    public static final int XYDIFF_INSIDE = -1;
    public static final int XYDIFF_ONLINE = 0;
    public static final int XYDIFF_OUTSIDE = 1;

    public int getXYDistance(PWInputPoint first, PWInputPoint last, Rect affectRect, int x, int y) {
        return XYDIFF_OUTSIDE;
    }


    /**
     * 获取本次 draw 需要更新的区域，并记录到 dirtyRects 里面。
     *
     * @param curPoint   本次需要draw 的输入的点。
     * @param dirtyRects 记录更新区域的 数组，区域可以有多个组成，也可以只有一个。
     * @return 返回本次增加的 更新区域的个数， 0 表示没有。如果不同的实现方法 更新的区域大小不同，可以重载这个方法。
     */
    public int getDrawPointRects(PWInputPoint curPoint, @Nullable RectArray dirtyRects) {
        if (dirtyRects != null) {
            //Rect result = new Rect();
            getPointsRect(curPoint, mPointsRect/*result*/);
            dirtyRects.addRect(mPointsRect/*result*/);
            return 1;
        }
        return 0;
    }


    // 20200323: we don't add sync,so all function must call at the same thread.
    protected static Rect mTempRect = new Rect();
    protected Rect mPointsRect = new Rect();

    // 20200320: reserved for feature use.
    //protected static RectF			mTempRectF = new RectF();

    /**
     * 获取一个 point 需要显示的区域（或者说是这个 point 可能影响的区域)，把对应的区域记录到 result 里面。
     *
     * @param curPoint
     * @param result
     */
    public static void getPointRect(PWInputPoint curPoint, Rect result) {
        int nag_radus = curPoint.getRadus(); //(int) (curPoint.getRadus() * 0.5f+0.5f);

        // 20200401: 修改了 radus 是 Int 之后我们不在这里调整 radus 1 到 0，我们在 draw point的时候调整。
        //if(nag_radus < 1) {  // 20200327: getRadus may return 0 for 1 pixel,so check here.
        //    nag_radus = 1;
        //}
        result.left = (curPoint.x - nag_radus);
        result.top = (curPoint.y - nag_radus);
        result.right = (curPoint.x + nag_radus);
        result.bottom = (curPoint.y + nag_radus);
    }

    public static void getPointsRect(PWInputPoint curPoint, Rect result) {
        PWInputPoint prevPoint = curPoint.getPrev();
        if (prevPoint != null) {
            // 20200319: PX30上面，刷新区域可以使用包含两个点的全部区域.
            getPointRect(prevPoint, mTempRect);
            getPointRect(curPoint, result);
            result.union(mTempRect);
        } else {
            getPointRect(curPoint, result);
        }
    }
}
